/*
 * $Id: config.c,v 1.48 2009-04-10 16:19:04 heas Exp $
 *
 * Copyright (c) 1995-1998 by Cisco systems, Inc.
 *
 * Permission to use, copy, modify, and distribute this software for
 * any purpose and without fee is hereby granted, provided that this
 * copyright and permission notice appear on all copies of the
 * software and supporting documentation, the name of Cisco Systems,
 * Inc. not be used in advertising or publicity pertaining to
 * distribution of the program without specific prior permission, and
 * notice be given in supporting documentation that modification,
 * copying and distribution is by permission of Cisco Systems, Inc.
 *
 * Cisco Systems, Inc. makes no representations about the suitability
 * of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
 * IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
 * WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE.
 */

#include "tac_plus.h"
#include <regex.h>
#ifndef REG_OK
#ifdef REG_NOERROR
#define REG_OK REG_NOERROR
#else
#define REG_OK 0
#endif
#endif

/*
 <config>		:=	<decl>*

 <decl>		:=	<top_level_decl> |
 <acl_decl> |
 <user_decl> |
 <group_decl> |
 <host_decl>

 <top_level_decl>	:=	<authen_default> |
 accounting file = <filename> |
 accounting syslog |
 key = <string> |
 logging = <syslog_fac>

 <authen_default>	:=	default authentication = file <filename>

 <permission>		:=	permit | deny

 <filename>		:=	<string>

 <password>		:=	<string>

 <syslog_fac>		:=	(auth|cron|daemon|ftp|kern|lpr|mail|news|
 syslog|user|uucp|local[0-7])

 <host_decl>		:=	host = <string> {
 key = <string>
 prompt = <string>
 enable = aceclnt|cleartext|des|
 file <filename/string>|
 nopassword|skey
 }

 <net_decl>		:=	net = <string> {
 key = <string>
 prompt = <string>
 enable = aceclnt|cleartext|des|
 file <filename/string>|
 nopassword|skey
 }

 <user_decl>		:=	user = <string> {
 [ default service = <permission> ]
 <user_attr>*
 <svc>*
 }

 <password_spec>	:=	nopassword |
 #ifdef ACECLNT
 aceclnt|
 #endif
 cleartext <password> |
 des <password> |
 file <filename> |
 #ifdef HAVE_PAM
 PAM |
 #endif
 skey

 <user_attr>		:=	name	= <string> |
 login	= <password_spec> |
 member	= <string> |
 expires	= <string> |
 arap	= cleartext <string> |
 chap	= cleartext <string> |
 #ifdef MSCHAP
 ms-chap	= cleartext <string> |
 #endif
 pap	= cleartext <string> |
 pap	= des <string> |
 #ifdef HAVE_PAM
 pap	= PAM |
 #endif
 opap	= cleartext <string> |
 global	= cleartext <string> |
 msg	= <string>
 before authorization = <string> |
 after authorization = <string>

 <svc>		:=	<svc_auth> | <cmd_auth>

 <cmd_auth>		:=	cmd = <string> {
 <cmd-match>*
 }

 <cmd-match>		:=	<permission> <string>

 <proto>		:=	XXX define this

 <svc_auth>		:=	service = ( arap | connection | exec |
 ppp protocol = <proto> | shell |
 slip | system | tty-daemon |
 <client defined> ) {
 [ default attribute = permit ]
 <attr_value_pair>*
 }

 <attr_value_pair>	:=	[ optional ] <string> = <string>

 */

static char sym_buf[MAX_INPUT_LINE_LEN]; /* parse buffer */
static int sym_pos = 0; /* current place in sym_buf */
static int sym_ch; /* current parse character */
static int sym_code; /* parser output */
static int sym_line = 1; /* current line number */
static FILE *cf = NULL; /* config file pointer */
static int sym_error = 0; /* a parsing error occurred */
static char *authen_default = NULL; /* top level authentication default */
static char *nopasswd_str = "nopassword";

/*
 * A host definition structure.
 * host-specific information e.g. per-host keys
 *
 * The first 2 fields (name and hash) are used by the hash table routines to
 * hash this structure into a table.  Do not (re)move them.
 */
typedef struct host {
	char *name; /* host name */
	void *hash; /* hash table next pointer */
	int line; /* line number defined on */
	char *key; /* host specific key */
	/*char *type;*//* host type - XXX: no idea what should be
	 * here */
	char *prompt; /* host login/username prompt string */
	char *enable; /* host enable password */
	int noenablepwd; /* host requires no enable password */
} HOST;

/*
 * A net definition structure.
 * net-specific information e.g. per-net keys
 *
 * This allows checking the IP address of the NAS based on regular expression matching
 *
 * The first 2 fields (name and hash) are used by the hash table routines to
 * hash this structure into a table.  Do not (re)move them.
 */
typedef struct net {
	char *name; /* host name */
	void *hash; /* hash table next pointer */
	int line; /* line number defined on */
	char *key; /* host specific key */
	char *prompt; /* host login/username prompt string */
	char *enable; /* host enable password */
	int noenablepwd; /* host requires no enable password */
} NET;

/*
 * A user or group definition
 * The first 2 fields (name and hash) are used by the hash table
 * routines to hash this structure into a table.  Move them at your peril
 */
typedef struct user {
	char *name; /* username */
	void *hash; /* hash table next pointer */
	int line; /* line number defined on */
	long flags; /* flags field */
#define FLAG_ISUSER  1		/* this structure represents a user */
#define FLAG_ISGROUP 2		/* this structure represents a group */
#define FLAG_SEEN    4		/* for circular definition detection */

	char *full_name; /* users full name */
	char *login; /* Login password */
	int nopasswd; /* user requires no password */
#ifdef ACLS
	char *acl; /* hosts (NASs) to allow / deny ACL */
#ifdef UENABLE
	char *enable; /* user enable pwd */
	int noenablepwd; /* user requires no enable password */
	char *enableacl; /* hosts (NASs) to allow/deny enabling */
#endif
#endif
	char *global; /* password to use if none set */
	char *member; /* group we are a member of */
	char *expires; /* expiration date */
	char *arap; /* our arap secret */
	char *pap; /* our pap secret */
	char *opap; /* our outbound pap secret */
	char *chap; /* our chap secret */
#ifdef MSCHAP
	char *mschap; /* our mschap secret */
#endif /* MSCHAP */
	char *msg; /* a message for this user */
	char *before_author; /* command to run before authorization */
	char *after_author; /* command to run after authorization */
	int svc_dflt; /* default authorization behaviour for svc or
	 * cmd */
	NODE *svcs; /* pointer to svc nodes */
#ifdef MAXSESS
	int maxsess; /* Max sessions/user */
#endif /* MAXSESS */
} USER;
typedef USER GROUP;

#ifdef ACLS

typedef struct filter {
	int isdeny;
	char *string;
	regex_t *string_reg;
	struct filter *next;
} FILTER;

typedef struct acl {
	char *name; /* acl name */
	void *hash; /* hash table next pointer */
	int line; /* line number defined on */
	NODE *nodes; /* list of entrys */
} ACL;
#endif

/* The following is to support Single Sign On functionality (SSO)
 * It is structured to be modular, allowing SSO modules to be added
 * as needed.  There is a single module defined here, SSO_LDAP, that
 * supports LDAP passthrough authentication and authorization
 */
#ifdef SSO_LDAP

#define SSO_LDAP_AUTHEN_TYPE_USER		1
#define SSO_LDAP_AUTHEN_TYPE_ACCOUNT	2

#define SSO_LDAP_CONN_TYPE_PLAIN		1
#define SSO_LDAP_CONN_TYPE_TLS			2

#define SSO_LDAP_BIND_TYPE_USER			1
#define SSO_LDAP_BIND_TYPE_ANON			2

typedef struct sso_ldap {
	char *base_dn;
	int authen_type;
	int conn_type;
	char *group_attr;
	char *member_attr;
	char *cmd_attr;
	int bind_type;
	char *bind_user;
	char *bind_pw;
};

#endif	/* SSO_LDAP */

/*
 * Only the first 2 fields (name and hash) are used by the hash table
 * routines to hash structures into a table.
 */
typedef union hash {
	struct user u;
#ifdef ACLS
	struct acl a;
#endif
	struct host h;
	struct net n;
} HASH;

void *grouptable[HASH_TAB_SIZE]; /* Table of group declarations */
void *usertable[HASH_TAB_SIZE]; /* Table of user declarations */
#ifdef ACLS
void *acltable[HASH_TAB_SIZE]; /* Table of ACL declarations */
#endif
void *hosttable[HASH_TAB_SIZE]; /* Table of host declarations */
void *nettable[HASH_TAB_SIZE]; /* Table of net declarations */

static VALUE cfg_get_hvalue(char *, int);
static VALUE cfg_get_nvalue(char *, int);
static VALUE cfg_get_value(char *, int, int, int);
static int circularity_check(void);
static void free_aclstruct(ACL *);
static void free_attrs(NODE *);
static void free_cmd_matches(NODE *);
static void free_hoststruct(HOST *);
static void free_netstruct(NET *);
static void free_svcs(NODE *);
static void free_userstruct(USER *);
static void getsym(void);
static VALUE get_value(USER *, int);
#ifdef ACLS
static int insert_acl_entry(ACL *, int);
static int parse_acl(void);
#endif
static NODE *parse_attrs(void);
static NODE *parse_cmd_matches(void);
static int parse_logging(void);
static int parse_host(void);
static int parse_net(void);
static int parse_opt_attr_default(void);
static int parse_opt_svc_default(void);
static int parse_permission(void);
static NODE *parse_svcs(void);
static int parse_user(void);
static void rch(void);
static void sym_get(void);

#ifdef __STDC__
#include <stdarg.h>		/* ANSI C, variable length args */
static void parse_error(char *fmt, ...)
#else
#include <varargs.h>		/* has 'vararg' definitions */
/* VARARGS2 */
static void
parse_error(fmt, va_alist)
char *fmt;
va_dcl /* no terminating semi-colon */
#endif
{
	char msg[256]; /* temporary string */
	va_list ap;

#ifdef __STDC__
	va_start(ap, fmt);
#else
	va_start(ap);
#endif
	vsprintf(msg, fmt, ap);
	va_end(ap);

	report(LOG_ERR, "%s", msg);
	fprintf(stderr, "Error: %s\n", msg);
	tac_exit(1);
}

char *
cfg_nodestring(int type)
{
	switch (type) {
		default:
			return ("unknown node type");
		case N_arg:
			return ("N_arg");
		case N_optarg:
			return ("N_optarg");
		case N_svc:
			return ("N_svc");
		case N_svc_exec:
			return ("N_svc_exec");
		case N_svc_slip:
			return ("N_svc_slip");
		case N_svc_ppp:
			return ("N_svc_ppp");
		case N_svc_arap:
			return ("N_svc_arap");
		case N_svc_cmd:
			return ("N_svc_cmd");
		case N_permit:
			return ("N_permit");
		case N_deny:
			return ("N_deny");
	}
}

static void free_attrs(NODE *node)
{
	NODE *next;

	while (node) {
		switch (node->type) {
			case N_optarg:
			case N_arg:
				if (debug & DEBUG_CLEAN_FLAG)
					report(LOG_DEBUG, "free_cmd_match %s %s", cfg_nodestring(node->type), node->value);
				break;
			default:
				report(LOG_ERR, "Illegal node type %s for free_attrs", cfg_nodestring(node->type));
				return;
		}

		free(node->value);
		next = node->next;
		free(node);
		node = next;
	}
}

#ifdef ACLS

static void
free_aclstruct(ACL *acl)
{
	NODE *last, *next = acl->nodes;

	last = next;

	if (debug & DEBUG_CLEAN_FLAG)
		report(LOG_DEBUG, "free_aclstruct %s", acl->name);

	while (next) {
		if (debug & DEBUG_CLEAN_FLAG)
			report(LOG_DEBUG, "free_aclstruct %s %s", acl->name, next->value);
		if (next->value)
			free(next->value);
		if (next->value1)
			free(next->value1);
		next = next->next;
		free(last);
		last = next;
	}

	if (acl->name)
		free(acl->name);
}
#endif

static void free_cmd_matches(NODE *node)
{
	NODE *next;

	while (node) {
		if (debug & DEBUG_CLEAN_FLAG)
			report(LOG_DEBUG, "free_cmd_match %s %s", cfg_nodestring(node->type), node->value);

		free(node->value); /* text */
		free(node->value1); /* regexp compiled text */
		next = node->next;
		free(node);
		node = next;
	}
}

static void free_hoststruct(HOST *host)
{
	if (debug & DEBUG_CLEAN_FLAG)
		report(LOG_DEBUG, "free %s", host->name);

	if (host->name)
		free(host->name);
	if (host->key)
		free(host->key);
	/* if (host->type)
	 free(host->type); */
	if (host->prompt)
		free(host->prompt);
	if (host->enable)
		free(host->enable);

	return;
}

static void free_netstruct(NET *net)
{
	if (debug & DEBUG_CLEAN_FLAG)
		report(LOG_DEBUG, "free %s", net->name);

	if (net->name)
		free(net->name);
	if (net->key)
		free(net->key);
	/* if (host->type)
	 free(host->type); */
	if (net->prompt)
		free(net->prompt);
	if (net->enable)
		free(net->enable);

	return;
}

static void free_svcs(NODE *node)
{
	NODE *next;

	while (node) {

		switch (node->type) {
			case N_svc_cmd:
				if (debug & DEBUG_CLEAN_FLAG)
					report(LOG_DEBUG, "free %s %s", cfg_nodestring(node->type), node->value);
				free(node->value); /* cmd name */
				free_cmd_matches(node->value1);
				next = node->next;
				free(node);
				node = next;
				continue;

			case N_svc:
			case N_svc_ppp:
				free(node->value1);
				/* FALL-THROUGH */
			case N_svc_exec:
			case N_svc_arap:
			case N_svc_slip:
				if (debug & DEBUG_CLEAN_FLAG)
					report(LOG_DEBUG, "free %s", cfg_nodestring(node->type));
				free_attrs(node->value);
				next = node->next;
				free(node);
				node = next;
				continue;

			default:
				report(LOG_ERR, "Illegal node type %d for free_svcs", node->type);
				return;
		}
	}
}

static void free_userstruct(USER *user)
{
	if (debug & DEBUG_CLEAN_FLAG)
		report(LOG_DEBUG, "free %s %s", (user->flags & FLAG_ISUSER) ? "user" : "group", user->name);

	if (user->name)
		free(user->name);
	if (user->full_name)
		free(user->full_name);
	if (user->login)
		free(user->login);
	if (user->member)
		free(user->member);
#ifdef ACLS
	if (user->acl)
		free(user->acl);
#ifdef UENABLE
	if (user->enable)
		free(user->enable);
	if (user->enableacl)
		free(user->enableacl);
#endif
#endif
	if (user->expires)
		free(user->expires);
	if (user->arap)
		free(user->arap);
	if (user->chap)
		free(user->chap);
#ifdef MSCHAP
	if (user->mschap)
		free(user->mschap);
#endif /* MSCHAP */
	if (user->pap)
		free(user->pap);
	if (user->opap)
		free(user->opap);
	if (user->global)
		free(user->global);
	if (user->msg)
		free(user->msg);
	if (user->before_author)
		free(user->before_author);
	if (user->after_author)
		free(user->after_author);
	free_svcs(user->svcs);
}

/*
 * Exported routines
 */

/* Free all allocated structures preparatory to re-reading the config file */
void cfg_clean_config(void)
{
	int i;
	USER *entry, *next;
	HOST *host_entry, *hn;
	NET *net_entry, *nn;
#ifdef ACLS
	ACL *aentry, *anext;
#endif

	if (authen_default) {
		free(authen_default);
		authen_default = NULL;
	}

	if (session.key) {
		free(session.key);
		session.key = NULL;
	}

	if (session.acctfile) {
		free(session.acctfile);
		session.acctfile = NULL;
	}
	session.flags = 0;

#ifdef ACLS
	/* clean the acltable */
	for (i = 0; i < HASH_TAB_SIZE; i++) {
		aentry = (ACL *) acltable[i];
		while (aentry) {
			anext = aentry->hash;
			free_aclstruct(aentry);
			free(aentry);
			aentry = anext;
		}
		acltable[i] = NULL;
	}
#endif

	/* the grouptable */
	for (i = 0; i < HASH_TAB_SIZE; i++) {
		entry = (USER *) grouptable[i];
		while (entry) {
			next = entry->hash;
			free_userstruct(entry);
			free(entry);
			entry = next;
		}
		grouptable[i] = NULL;
	}

	/* clean the hosttable */
	for (i = 0; i < HASH_TAB_SIZE; i++) {
		host_entry = (HOST *) hosttable[i];
		while (host_entry) {
			hn = host_entry->hash;
			free_hoststruct(host_entry);
			free(host_entry);
			host_entry = hn;
		}
		hosttable[i] = NULL;
	}

	/* clean the nettable */
	for (i = 0; i < HASH_TAB_SIZE; i++) {
		net_entry = (NET *) nettable[i];
		while (net_entry) {
			nn = net_entry->hash;
			free_netstruct(net_entry);
			free(net_entry);
			net_entry = nn;
		}
		nettable[i] = NULL;
	}

	/* the usertable */
	for (i = 0; i < HASH_TAB_SIZE; i++) {
		entry = (USER *) usertable[i];
		while (entry) {
			next = entry->hash;
			free_userstruct(entry);
			free(entry);
			entry = next;
		}
		usertable[i] = NULL;
	}
}

static int parse_permission(void)
{
	int symbol = sym_code;

	if (sym_code != S_permit && sym_code != S_deny) {
		parse_error("expecting permit or deny but found '%s' on line %d", sym_buf, sym_line);
		return (0);
	}
	sym_get();

	return (symbol);
}

static int parse(int symbol)
{
	if (sym_code != symbol) {
		parse_error("expecting '%s' but found '%s' on line %d", (symbol == S_string ? "string" : codestring(symbol)), sym_buf,
				sym_line);
		return (1);
	}
	sym_get();
	return (0);
}

static int parse_opt_svc_default(void)
{
	if (sym_code != S_default) {
		return (0);
	}

	parse(S_default);
	parse(S_svc);
	parse(S_separator);
	if (sym_code == S_permit) {
		parse(S_permit);
		return (S_permit);
	}
	parse(S_deny);
	return (S_deny);
}

static int parse_opt_attr_default(void)
{
	if (sym_code != S_default)
		return (S_deny);

	parse(S_default);
	parse(S_attr);
	parse(S_separator);
	parse(S_permit);
	return (S_permit);
}

#ifdef ACLS

/* insert an acl entry into the named acl node list */
static int
insert_acl_entry(ACL *acl, int isdeny)
{
	char buf[256];
	NODE *next = acl->nodes;
	NODE *entry = (NODE *) tac_malloc(sizeof (NODE));
	int ecode;

	memset(entry, 0, sizeof (NODE));

	entry->type = isdeny;
	entry->value = tac_strdup(sym_buf);
	entry->line = sym_line;

	/* compile the regex */
	entry->value1 = tac_malloc(sizeof (regex_t));
	ecode = regcomp((regex_t *) entry->value1, (char *) entry->value,
			(REG_EXTENDED | REG_NOSUB));
	if (ecode) {
		regerror(ecode, (regex_t *) entry->value1, buf, 256);
		report(LOG_ERR, "in regex %s on line %d", sym_buf, sym_line);
		report(LOG_ERR, "regex compile failed: %s", buf);
		tac_exit(1);
	}

	if (acl->nodes == NULL) {
		acl->nodes = entry;
		return (0);
	}

	while (next->next != NULL) {
		next = next->next;
	}
	next->next = entry;

	return (0);
}

/* parse the acl = NAME { allow = regex  deny = regex } */
static int
parse_acl(void)
{
	ACL *n;
	ACL *acl = (ACL *) tac_malloc(sizeof (ACL));
	int isdeny = S_permit;

	memset(acl, 0, sizeof (ACL));

	sym_get();
	parse(S_separator);
	acl->name = tac_strdup(sym_buf);
	acl->line = sym_line;

	n = hash_add_entry(acltable, (void *) acl);

	if (n) {
		parse_error("multiply defined acl %s on lines %d and %d", acl->name,
				n->line, sym_line);
		return (1);
	}
	sym_get();
	parse(S_openbra);

	while (1) {
		switch (sym_code) {
			case S_eof:
				return (0);

			case S_deny:
				isdeny = S_deny;
			case S_permit:
				sym_get();
				parse(S_separator);
				insert_acl_entry(acl, isdeny);
				parse(S_string);
				isdeny = S_permit;
				continue;

			case S_closebra:
				parse(S_closebra);
				return (0);

			default:
				parse_error("Unrecognised keyword %s for acl on line %d", sym_buf,
						sym_line);

				return (0);
		}
	}
}
#endif

/*
 * Parse lines in the config file, creating data structures.
 * Return 1 on error, otherwise 0
 */
static int parse_decls()
{

	sym_code = 0;
	rch();

#ifdef ACLS
	memset(acltable, 0, sizeof (acltable));
#endif
	memset(grouptable, 0, sizeof (grouptable));
	memset(usertable, 0, sizeof (usertable));
	memset(hosttable, 0, sizeof (hosttable));

	sym_get();

	/* Top level of parser */
	while (1) {

		switch (sym_code) {
			case S_eof:
				return (0);

			case S_accounting:
				sym_get();

				switch (sym_code) {
					case S_file:
						parse(S_file);
						parse(S_separator);
						if (session.acctfile != NULL)
							free(session.acctfile);
						session.acctfile = tac_strdup(sym_buf);
						break;

					case S_syslog:
						session.flags |= SESS_FLAG_ACCTSYSL;
						break;
				}

				sym_get();
				continue;

			case S_default:
				sym_get();
				switch (sym_code) {
					default:
						parse_error("Expecting default authentication on line %d", sym_line);
						return (1);

					case S_authentication:
						if (authen_default) {
							parse_error("Multiply defined authentication default on "
									"line %d", sym_line);
							return (1);
						}
						parse(S_authentication);
						parse(S_separator);
						parse(S_file);
						authen_default = tac_strdup(sym_buf);
						sym_get();
						continue;
				}

			case S_key:
				/* Process a key declaration. */
				sym_get();
				parse(S_separator);
				if (session.key) {
					parse_error("multiply defined key on lines %d and %d", session.keyline, sym_line);
					return (1);
				}
				session.key = tac_strdup(sym_buf);
				session.keyline = sym_line;
				sym_get();
				continue;

			case S_host:
				parse_host();
				continue;

			case S_net:
				parse_net();
				continue;

			case S_user:
			case S_group:
				parse_user();
				continue;

#ifdef ACLS
			case S_acl:
				parse_acl();
				continue;
#endif

			case S_logging:
				parse_logging();
				continue;

			default:
				parse_error("Unrecognised token %s on line %d", sym_buf, sym_line);
				return (1);
		}
	}
}

/*
 * Assign a value to a field.  Issue an error message and return 1 if
 * it has already been assigned.  This is a macro because I was sick of
 * repeating the same code fragment over and over.
 */
#define ASSIGN(field) \
sym_get(); parse(S_separator); if (field) { \
	parse_error("Duplicate value for %s %s and %s on line %d", \
		    codestring(sym_code), field, sym_buf, sym_line); \
	tac_exit(1); \
    } \
    field = tac_strdup(sym_buf);

static int parse_host(void)
{
	HOST *h;
	HOST *host = (HOST *) tac_malloc(sizeof (HOST));
	char buf[MAX_INPUT_LINE_LEN];

	memset(host, 0, sizeof (HOST));

	sym_get();
	parse(S_separator);
	host->name = tac_strdup(sym_buf);
	host->line = sym_line;

	h = hash_add_entry(hosttable, (void *) host);

	if (h) {
		parse_error("multiply defined %s on lines %d and %d", host->name, h->line, sym_line);
		return (1);
	}

	sym_get();
	parse(S_openbra);

	while (1) {
		switch (sym_code) {
			case S_eof:
				return (0);
			case S_key:
				ASSIGN(host->key)
						;
				sym_get();
				continue;

			case S_prompt:
				ASSIGN(host->prompt)
						;
				sym_get();
				continue;

			case S_enable:
				if (host->enable) {
					parse_error("Duplicate value for %s %s and %s on line %d", codestring(sym_code), host->enable, sym_buf, sym_line);
					tac_exit(1);
				}
				sym_get();
				parse(S_separator);
				switch (sym_code) {

					case S_cleartext:
					case S_des:
						sprintf(buf, "%s ", sym_buf);
						sym_get();
						/* XXX: naughty, this should check the size */
						strcat(buf, sym_buf);
						host->enable = tac_strdup(buf);
						break;

					case S_nopasswd:
						/* set to dummy string, so that we detect a duplicate
						 * password definition attempt
						 */
						host->enable = tac_strdup(nopasswd_str);
						host->noenablepwd = 1;
						break;

					default:
						parse_error("expecting 'cleartext' or 'des' keyword after "
								"'enable =' on line %d", sym_line);
				}
				sym_get();
				continue;

			case S_closebra:
				parse(S_closebra);
				return (0);

			default:
				parse_error("Unrecognised keyword %s for host %s on line %d", sym_buf, host->name, sym_line);

				return (0);
		}
	}
}

static int parse_net(void)
{
	NET *n;
	NET *net = (NET *) tac_malloc(sizeof (NET));
	char buf[MAX_INPUT_LINE_LEN];

	memset(net, 0, sizeof (NET));

	sym_get();
	parse(S_separator);
	net->name = tac_strdup(sym_buf);
	net->line = sym_line;

	n = hash_add_entry(nettable, (void *) net);

	if (n) {
		parse_error("multiply defined %s on lines %d and %d", net->name, n->line, sym_line);
		return (1);
	}

	sym_get();
	parse(S_openbra);

	while (1) {
		switch (sym_code) {
			case S_eof:
				return (0);
			case S_key:
				ASSIGN(net->key)
						;
				sym_get();
				continue;

			case S_prompt:
				ASSIGN(net->prompt)
						;
				sym_get();
				continue;

			case S_enable:
				if (net->enable) {
					parse_error("Duplicate value for %s %s and %s on line %d", codestring(sym_code), net->enable, sym_buf, sym_line);
					tac_exit(1);
				}
				sym_get();
				parse(S_separator);
				switch (sym_code) {

					case S_cleartext:
					case S_des:
						sprintf(buf, "%s ", sym_buf);
						sym_get();
						/* XXX: naughty, this should check the size */
						strcat(buf, sym_buf);
						net->enable = tac_strdup(buf);
						break;

					case S_nopasswd:
						/* set to dummy string, so that we detect a duplicate
						 * password definition attempt
						 */
						net->enable = tac_strdup(nopasswd_str);
						net->noenablepwd = 1;
						break;

					default:
						parse_error("expecting 'cleartext' or 'des' keyword after "
								"'enable =' on line %d", sym_line);
				}
				sym_get();
				continue;

			case S_closebra:
				parse(S_closebra);
				return (0);

			default:
				parse_error("Unrecognised keyword %s for net %s on line %d", sym_buf, net->name, sym_line);

				return (0);
		}
	}
}

/*
 * Parse logging statement.
 * Return 1 on error, otherwise 0
 */
static int parse_logging(void)
{

	struct _facs {
		char *name;
		int level;
	} facs[] = {
		{ "auth", LOG_AUTH},
		{ "cron", LOG_CRON},
		{ "daemon", LOG_DAEMON},
#ifdef LOG_FTP
		{ "ftp", LOG_FTP},
#endif
		{ "kern", LOG_KERN},
		{ "local0", LOG_LOCAL0},
		{ "local1", LOG_LOCAL1},
		{ "local2", LOG_LOCAL2},
		{ "local3", LOG_LOCAL3},
		{ "local4", LOG_LOCAL4},
		{ "local5", LOG_LOCAL5},
		{ "local6", LOG_LOCAL6},
		{ "local7",
			LOG_LOCAL7},
		{ "lpr", LOG_LPR},
		{ "mail", LOG_MAIL},
		{ "news", LOG_NEWS},
		{ "syslog", LOG_SYSLOG},
		{ "user",
			LOG_USER},
		{ "uucp", LOG_UUCP},
		{ NULL, 0}
	};
	int fac;

	sym_get();
	parse(S_separator);

	for (fac = 0; sym_code == S_string && facs[fac].name != NULL; fac++) {
		if (strcmp(sym_buf, facs[fac].name) == 0)
			break;
	}
	if (facs[fac].name == NULL)
		parse_error("expecting syslog facility on line %d, got %s", sym_line, sym_buf);

	facility = facs[fac].level;
	closelog();
	open_logfile();

	sym_get();
	return (0);
}

static int parse_user(void)
{
	USER *n;
	int isuser;
	USER *user = (USER *) tac_malloc(sizeof (USER));
	int save_sym;
	char **fieldp;
	char buf[MAX_INPUT_LINE_LEN];

	fieldp = NULL;
	memset(user, 0, sizeof (USER));

	isuser = (sym_code == S_user);

	sym_get();
	parse(S_separator);
	user->name = tac_strdup(sym_buf);
	user->line = sym_line;

	if (isuser) {
		user->flags |= FLAG_ISUSER;
		n = hash_add_entry(usertable, (void *) user);
	} else {
		user->flags |= FLAG_ISGROUP;
		n = hash_add_entry(grouptable, (void *) user);
	}

	if (n) {
		parse_error("multiply defined %s %s on lines %d and %d", isuser ? "user" : "group", user->name, n->line, sym_line);
		return (1);
	}
	sym_get();
	parse(S_openbra);

	/* Is the default deny for svcs or cmds to be overridden? */
	user->svc_dflt = parse_opt_svc_default();

	while (1) {
		switch (sym_code) {
			case S_eof:
				return (0);

			case S_before:
				sym_get();
				parse(S_authorization);
				if (user->before_author)
					free(user->before_author);
				user->before_author = tac_strdup(sym_buf);
				sym_get();
				continue;

			case S_after:
				sym_get();
				parse(S_authorization);
				if (user->after_author)
					free(user->after_author);
				user->after_author = tac_strdup(sym_buf);
				sym_get();
				continue;

			case S_svc:
			case S_cmd:
				if (user->svcs) {
					/*
					 * Already parsed some services/commands. Thanks to Gabor Kiss
					 * who found this bug.
					 */
					NODE *p;
					for (p = user->svcs; p->next; p = p->next)
						/* NULL STMT */;
					p->next = parse_svcs();
				} else {
					user->svcs = parse_svcs();
				}
				continue;

			case S_login:
				if (user->login) {
					parse_error("Duplicate value for %s %s and %s on line %d", codestring(sym_code), user->login, sym_buf, sym_line);
					tac_exit(1);
				}
				sym_get();
				parse(S_separator);
				switch (sym_code) {

#ifdef SKEY
					case S_skey:
						user->login = tac_strdup(sym_buf);
						break;
#endif

#ifdef ACECLNT
					case S_aceclnt:
						user->login = tac_strdup(sym_buf);
						break;
#endif

#ifdef HAVE_PAM
					case S_pam:
						user->login = tac_strdup(sym_buf);
						break;
#endif

					case S_nopasswd:
						/*
						 * set to dummy string, so that we detect a duplicate
						 * password definition attempt
						 */
						user->login = tac_strdup(nopasswd_str);
						user->nopasswd = 1;
						break;

					case S_file:
					case S_cleartext:
					case S_des:
						sprintf(buf, "%s ", sym_buf);
						sym_get();
						strcat(buf, sym_buf);
						user->login = tac_strdup(buf);
						break;

					default:
						parse_error("expecting 'file', 'cleartext', 'nopassword', "
#ifdef SKEY
								"'skey', "
#endif
#ifdef ACECLNT
								"'aceclnt', "
#endif
#ifdef HAVE_PAM
								"'PAM', "
#endif
								"or 'des' keyword after 'login =' on line %d", sym_line);
				}
				sym_get();
				continue;

			case S_pap:
				if (user->pap) {
					parse_error("Duplicate value for %s %s and %s on line %d", codestring(sym_code), user->pap, sym_buf, sym_line);
					tac_exit(1);
				}
				sym_get();
				parse(S_separator);
				switch (sym_code) {

#ifdef HAVE_PAM
					case S_pam:
						user->pap = tac_strdup(sym_buf);
						break;
#endif

					case S_file:
					case S_cleartext:
					case S_des:
						sprintf(buf, "%s ", sym_buf);
						sym_get();
						strcat(buf, sym_buf);
						user->pap = tac_strdup(buf);
						break;

					default:
						parse_error("expecting 'cleartext', "
#ifdef HAVE_PAM
								"'PAM', "
#endif
								"or 'des' keyword after "
								"'pap =' on line %d", sym_line);
				}
				sym_get();
				continue;

#ifdef ACLS
			case S_acl:
				ASSIGN(user->acl);
				sym_get();
				continue;

#ifdef UENABLE
			case S_enable:
				sym_get();
				parse(S_separator);

				switch (sym_code) {
					case S_file:
					case S_cleartext:
					case S_des:
						sprintf(buf, "%s ", sym_buf);
						sym_get();
						strcat(buf, sym_buf);
						user->enable = tac_strdup(buf);
						break;

					case S_nopasswd:
						/* set to dummy string, so that we detect a duplicate
						 * password definition attempt
						 */
						user->enable = tac_strdup(nopasswd_str);
						user->noenablepwd = 1;
						break;

#ifdef SKEY
					case S_skey:
						user->enable = tac_strdup(sym_buf);
						break;
#endif
#ifdef ACECLNT
					case S_aceclnt:
						user->enable = tac_strdup(sym_buf);
						break;
#endif

					default:
						parse_error("expecting 'file', 'cleartext', 'nopassword', "
#ifdef SKEY
								"'skey', "
#endif
#ifdef ACECLNT
								"'aceclnt', "
#endif
								"or 'des' keyword after 'enable =' on line %d",
								sym_line);
				}
				sym_get();
				continue;

			case S_enableacl:
				ASSIGN(user->enableacl);
				sym_get();
				continue;
#endif
#endif

			case S_name:
				ASSIGN(user->full_name)
						;
				sym_get();
				continue;

			case S_member:
				ASSIGN(user->member)
						;
				sym_get();
				continue;

			case S_expires:
				ASSIGN(user->expires)
						;
				sym_get();
				continue;

			case S_message:
				ASSIGN(user->msg)
						;
				sym_get();
				continue;

			case S_arap:
			case S_chap:
#ifdef MSCHAP
			case S_mschap:
#endif /* MSCHAP */
			case S_opap:
			case S_global:
				save_sym = sym_code;
				sym_get();
				parse(S_separator);
				sprintf(buf, "%s ", sym_buf);
				parse(S_cleartext);
				strcat(buf, sym_buf);

				if (save_sym == S_arap)
					fieldp = &user->arap;
				if (save_sym == S_chap)
					fieldp = &user->chap;
#ifdef MSCHAP
				if (save_sym == S_mschap)
					fieldp = &user->mschap;
#endif /* MSCHAP */
				if (save_sym == S_pap)
					fieldp = &user->pap;
				if (save_sym == S_opap)
					fieldp = &user->opap;
				if (save_sym == S_global)
					fieldp = &user->global;

				if (*fieldp) {
					parse_error("Duplicate value for %s %s and %s on line %d", codestring(save_sym), *fieldp, sym_buf, sym_line);
					tac_exit(1);
				}
				*fieldp = tac_strdup(buf);
				sym_get();
				continue;

			case S_closebra:
				parse(S_closebra);
				return (0);

#ifdef MAXSESS
			case S_maxsess:
				sym_get();
				parse(S_separator);
				if (sscanf(sym_buf, "%d", &user->maxsess) != 1) {
					parse_error("expecting integer, found '%s' on line %d",
							sym_buf, sym_line);
				}
				sym_get();
				continue;
#endif /* MAXSESS */

			default:
				if (STREQ(sym_buf, "password")) {
					fprintf(stderr, "\npassword = <string> is obsolete. Use login = des <string>\n");
				}
				parse_error("Unrecognised keyword %s for user on line %d", sym_buf, sym_line);

				return (0);
		}
	}
}

static NODE *
parse_svcs(void)
{
	NODE *result;

	switch (sym_code) {
		default:
			return (NULL);
		case S_svc:
		case S_cmd:
			break;
	}

	result = (NODE *) tac_malloc(sizeof (NODE));

	memset(result, 0, sizeof (NODE));
	result->line = sym_line;

	/* cmd declaration */
	if (sym_code == S_cmd) {
		parse(S_cmd);
		parse(S_separator);
		result->value = tac_strdup(sym_buf);

		sym_get();
		parse(S_openbra);

		result->value1 = parse_cmd_matches();
		result->type = N_svc_cmd;

		parse(S_closebra);
		result->next = parse_svcs();
		return (result);
	}

	/* svc declaration */
	parse(S_svc);
	parse(S_separator);
	switch (sym_code) {
		case S_string:
			result->type = N_svc;
			/* XXX should perhaps check that this is an allowable service name */
			result->value1 = tac_strdup(sym_buf);
			break;
		case S_exec:
			result->type = N_svc_exec;
			break;
		case S_arap:
			result->type = N_svc_arap;
			break;
		case S_slip:
			result->type = N_svc_slip;
			break;
		case S_ppp:
			result->type = N_svc_ppp;
			parse(S_ppp);
			parse(S_protocol);
			parse(S_separator);
			/* XXX Should perhaps check that this is a known PPP protocol name */
			result->value1 = tac_strdup(sym_buf);
			break;
		default:
			parse_error("expecting service type but found %s on line %d", sym_buf, sym_line);
			return (NULL);
	}
	sym_get();
	parse(S_openbra);
	result->dflt = parse_opt_attr_default();
	result->value = parse_attrs();
	parse(S_closebra);
	result->next = parse_svcs();
	return (result);
}

/* <cmd-match>	 := <permission> <string> */
static NODE *
parse_cmd_matches(void)
{
	char buf[256];
	NODE *result;
	int ecode;

	if (sym_code != S_permit && sym_code != S_deny) {
		return (NULL);
	}
	result = (NODE *) tac_malloc(sizeof (NODE));

	memset(result, 0, sizeof (NODE));
	result->line = sym_line;

	result->type = (parse_permission() == S_permit) ? N_permit : N_deny;
	result->value = tac_strdup(sym_buf);

	result->value1 = tac_malloc(sizeof (regex_t));
	ecode = regcomp((regex_t *) result->value1, (char *) result->value, (REG_EXTENDED | REG_NOSUB));
	if (ecode) {
		regerror(ecode, (regex_t *) result->value1, buf, 256);
		report(LOG_ERR, "in regex %s on line %d", sym_buf, sym_line);
		report(LOG_ERR, "regex compile failed: %s", buf);
		tac_exit(1);
	}
	sym_get();

	result->next = parse_cmd_matches();

	return (result);
}

static NODE *
parse_attrs(void)
{
	NODE *result;
	char buf[MAX_INPUT_LINE_LEN];
	int optional = 0;

	if (sym_code == S_closebra) {
		return (NULL);
	}
	result = (NODE *) tac_malloc(sizeof (NODE));

	memset(result, 0, sizeof (NODE));
	result->line = sym_line;

	if (sym_code == S_optional) {
		optional++;
		sym_get();
	}
	result->type = optional ? N_optarg : N_arg;

#ifdef ACLS
	/*
	 * "acl" is an acceptable AV for service=exec and may as well be permitted
	 * for any other service.  I did not know this when I defined "acl" for
	 * connection ACLs.  So, hack it to be a string here.  If the parser were
	 * half-way decent, acl just wouldnt be a keyword here.
	 */
	if (sym_code == S_acl)
		sym_code = S_string;
#endif
	strcpy(buf, sym_buf);
	parse(S_string);
	strcat(buf, sym_buf);
	parse(S_separator);
	strcat(buf, sym_buf);
	parse(S_string);

	result->value = tac_strdup(buf);
	result->next = parse_attrs();
	return (result);
}

static void sym_get(void)
{
	getsym();

	if (debug & DEBUG_PARSE_FLAG) {
		report(LOG_DEBUG, "line=%d sym=%s code=%d buf='%s'", sym_line, codestring(sym_code), sym_code, sym_buf);
	}
}

static char *
sym_buf_add(char c)
{
	if (sym_pos >= MAX_INPUT_LINE_LEN) {
		sym_buf[MAX_INPUT_LINE_LEN - 1] = '\0';
		if (debug & DEBUG_PARSE_FLAG) {
			report(LOG_DEBUG, "line too long: line=%d sym=%s code=%d buf='%s'", sym_line, codestring(sym_code), sym_code, sym_buf);
		}
		return (NULL);
	}

	sym_buf[sym_pos++] = c;
	return (sym_buf);
}

static void getsym(void)
{

next:
	switch (sym_ch) {

		case EOF:
			sym_code = S_eof;
			return;

		case '\n':
			sym_line++;
			rch();
			goto next;

		case '\t':
		case ' ':
			while (sym_ch == ' ' || sym_ch == '\t')
				rch();
			goto next;

		case '=':
			strcpy(sym_buf, "=");
			sym_code = S_separator;
			rch();
			return;

		case '{':
			strcpy(sym_buf, "{");
			sym_code = S_openbra;
			rch();
			return;

		case '}':
			strcpy(sym_buf, "}");
			sym_code = S_closebra;
			rch();
			return;

		case '#':
			while ((sym_ch != '\n') && (sym_ch != EOF))
				rch();
			goto next;

		case '"':
			rch();
			sym_pos = 0;
			while (1) {

				if (sym_ch == '"') {
					break;
				}

				/* backslash-double-quote is supported inside strings */
				/* also allow \n */
				if (sym_ch == '\\') {
					rch();
					switch (sym_ch) {
						case 'n':
							/* preserve the slash for \n */
							if (!sym_buf_add('\\')) {
								sym_code = S_unknown;
								rch();
								return;
							}

							/* fall through */
						case '"':
						case '\\':
							if (!sym_buf_add(sym_ch)) {
								sym_code = S_unknown;
								rch();
								return;
							}
							rch();
							continue;
						default:
							sym_code = S_unknown;
							rch();
							return;
					}
				}
				if (!sym_buf_add(sym_ch)) {
					sym_code = S_unknown;
					rch();
					return;
				}
				rch();
			}
			rch();

			if (!sym_buf_add('\0')) {
				sym_code = S_unknown;
				rch();
				return;
			}
			sym_code = S_string;
			return;

		default:
			sym_pos = 0;
			while (sym_ch != '\t' && sym_ch != ' ' && sym_ch != '=' && sym_ch != '\n') {

				if (!sym_buf_add(sym_ch)) {
					sym_code = S_unknown;
					rch();
					return;
				}
				rch();
			}

			if (!sym_buf_add('\0')) {
				sym_code = S_unknown;
				rch();
				return;
			}
			sym_code = keycode(sym_buf);
			if (sym_code == S_unknown)
				sym_code = S_string;
			return;
	}
}

static void rch(void)
{
	if (sym_error) {
		sym_ch = EOF;
		return;
	}
	sym_ch = getc(cf);

	if (parse_only && sym_ch != EOF)
		fprintf(stderr, "%c", sym_ch);
}

/* For a user or group, find the value of a field.  Does not recurse. */
static VALUE get_value(USER *user, int field)
{
	VALUE v;

	memset(&v, 0, sizeof (VALUE));

	if (!user) {
		parse_error("get_value: illegal user");
		return (v);
	}
	switch (field) {
		case S_name:
			v.pval = user->name;
			break;

		case S_login:
			v.pval = user->login;
			break;

		case S_global:
			v.pval = user->global;
			break;

		case S_member:
			v.pval = user->member;
			break;

		case S_expires:
			v.pval = user->expires;
			break;

		case S_arap:
			v.pval = user->arap;
			break;

		case S_chap:
			v.pval = user->chap;
			break;

#ifdef MSCHAP
		case S_mschap:
			v.pval = user->mschap;
			break;
#endif /* MSCHAP */

#ifdef ACLS
		case S_acl:
			v.pval = user->acl;
			break;
#ifdef UENABLE
		case S_enable:
			v.pval = user->enable;
			break;
		case S_noenablepwd:
			v.intval = user->noenablepwd;
			break;
		case S_enableacl:
			v.pval = user->enableacl;
			break;
#endif
#endif

		case S_pap:
			v.pval = user->pap;
			break;

		case S_opap:
			v.pval = user->opap;
			break;

		case S_message:
			v.pval = user->msg;
			break;

		case S_svc:
			v.pval = user->svcs;
			break;

		case S_before:
			v.pval = user->before_author;
			break;

		case S_after:
			v.pval = user->after_author;
			break;

		case S_svc_dflt:
			v.intval = user->svc_dflt;
			break;

#ifdef MAXSESS
		case S_maxsess:
			v.intval = user->maxsess;
			break;
#endif

		case S_nopasswd:
			v.intval = user->nopasswd;
			break;

		default:
			report(LOG_ERR, "get_value: unknown field %d", field);
			break;
	}
	return (v);
}

/* For host, find value of field.  Is not recursive */
VALUE get_hvalue(HOST *host, int field)
{
	VALUE v;

	memset(&v, 0, sizeof (VALUE));
	if (!host) {
		parse_error("get_hvalue: illegal host");
		return (v);
	}
	switch (field) {
		case S_name:
			v.pval = host->name;
			break;

		case S_key:
			v.pval = host->key;
			break;

			/* XXX
			 case S_type:
			 v.pval = host->type;
			 break;
			 */

		case S_prompt:
			v.pval = host->prompt;
			break;

		case S_enable:
			v.pval = host->enable;
			break;

#ifdef UENABLE
		case S_noenablepwd:
			v.intval = host->noenablepwd;
			break;
#endif

		default:
			report(LOG_ERR, "get_value: unknown field %d", field);
			break;
	}
	return (v);
}

/* For net, find value of field.  Is not recursive */
VALUE get_nvalue(NET *net, int field)
{
	VALUE v;

	memset(&v, 0, sizeof (VALUE));
	if (!net) {
		parse_error("get_nvalue: illegal net");
		return (v);
	}
	switch (field) {
		case S_name:
			v.pval = net->name;
			break;

		case S_key:
			v.pval = net->key;
			break;

			/* XXX
			 case S_type:
			 v.pval = host->type;
			 break;
			 */

		case S_prompt:
			v.pval = net->prompt;
			break;

		case S_enable:
			v.pval = net->enable;
			break;

#ifdef UENABLE
		case S_noenablepwd:
			v.intval = net->noenablepwd;
			break;
#endif

		default:
			report(LOG_ERR, "get_value: unknown field %d", field);
			break;
	}
	return (v);
}

/*
 * For each user, check the user does not circularly reference a group.
 * Return 1 if it does.
 */
static int circularity_check(void)
{
	USER *user, *entry, *group;
	USER **users = (USER **) hash_get_entries(usertable);
	USER **groups = (USER **) hash_get_entries(grouptable);
	USER **p, **q;

	/* users */
	for (p = users; *p; p++) {
		user = *p;

		if (debug & DEBUG_PARSE_FLAG)
			report(LOG_DEBUG, "circularity_check: user=%s", user->name);

		/* Initialise all groups "seen" flags to zero */
		for (q = groups; *q; q++) {
			group = *q;
			group->flags &= ~FLAG_SEEN;
		}

		entry = user;

		while (entry) {
			/* check groups we are a member of */
			char *groupname = entry->member;

			if (debug & DEBUG_PARSE_FLAG)
				report(LOG_DEBUG, "\tmember of group %s", groupname ? groupname : "<none>");

			/* if not a member of any groups, go on to next user */
			if (!groupname)
				break;

			group = (USER *) hash_lookup(grouptable, groupname);
			if (!group) {
				report(LOG_ERR, "%s=%s, group %s does not exist", (entry->flags & FLAG_ISUSER) ? "user" : "group", entry->name,
						groupname);
				free(users);
				free(groups);
				return (1);
			}
			if (group->flags & FLAG_SEEN) {
				report(LOG_ERR, "recursively defined groups");

				/* print all seen "groups" */
				for (q = groups; *q; q++) {
					group = *q;
					if (group->flags & FLAG_SEEN)
						report(LOG_ERR, "%s", group->name);
				}
				free(users);
				free(groups);
				return (1);
			}
			group->flags |= FLAG_SEEN; /* mark group as seen */
			entry = group;
		}
	}
	free(users);
	free(groups);
	return (0);
}

/*
 * Return a value for a group or user (isuser says if this name is a group or
 * a user name).
 *
 * If no value exists, and recurse is true, also check groups the user is a
 * member of, recursively.
 *
 * Returns void * because it can return a string or a node pointer (should
 * really return a union pointer).
 */
static VALUE cfg_get_value(char *name, int isuser, int attr, int recurse)
{
	USER *user, *group;
	VALUE value;

	memset(&value, 0, sizeof (VALUE));

	if (debug & DEBUG_CONFIG_FLAG)
		report(LOG_DEBUG, "cfg_get_value: name=%s isuser=%d attr=%s rec=%d", name, isuser, codestring(attr), recurse);

	/* TODO - This is where to put the passthrough user/group lookup when using LDAP for
	 * user/group membership
	 */

	/* find the user/group entry */
	user = (USER *) hash_lookup(isuser ? usertable : grouptable, name);

	if (!user) {
		if (debug & DEBUG_CONFIG_FLAG)
			report(LOG_DEBUG, "cfg_get_value: no user/group named %s", name);
		return (value);
	}

	/* found the entry. Lookup value from attr=value */
	value = get_value(user, attr);

	if (value.pval || !recurse) {
		return (value);
	}
	/* no value. Check containing group */
	if (user->member)
		group = (USER *) hash_lookup(grouptable, user->member);
	else
		group = NULL;

	while (group) {
		if (debug & DEBUG_CONFIG_FLAG)
			report(LOG_DEBUG, "cfg_get_value: recurse group = %s", group->name);

		value = get_value(group, attr);
		if (value.pval) {
			return (value);
		}
		/* still nothing. Check containing group and so on */

		if (group->member)
			group = (USER *) hash_lookup(grouptable, group->member);
		else
			group = NULL;
	}

	/* no value for this user or her containing groups */
	memset(&value, 0, sizeof (VALUE));
	return (value);
}

/* For getting host values */
static VALUE cfg_get_hvalue(char *name, int attr)
{
	HOST *host;
	VALUE value;

	memset(&value, 0, sizeof (VALUE));

	if (debug & DEBUG_CONFIG_FLAG)
		report(LOG_DEBUG, "cfg_get_hvalue: name=%s attr=%s ", name, codestring(attr));

	/* find the host entry in hash table */
	host = (HOST *) hash_lookup(hosttable, name);

	if (!host) {
		if (debug & DEBUG_CONFIG_FLAG)
			report(LOG_DEBUG, "cfg_get_hvalue: no host named %s", name);
		return (value);
	}

	/* found the entry. Lookup value from attr=value */
	value = get_hvalue(host, attr);

	if (value.pval)
		return (value);

	/* No any value for this host */
	memset(&value, 0, sizeof (VALUE));
	return (value);
}

/* For getting net values */
static VALUE cfg_get_nvalue(char *name, int attr)
{
	NET *net;
	VALUE value;
	int i, j, network, mask, return_val, peer_int_addr;
	char cidr[25];
	struct in_addr peer_addr;

	memset(&value, 0, sizeof (VALUE));

	if (debug & DEBUG_CONFIG_FLAG)
		report(LOG_DEBUG, "cfg_get_nvalue: name=%s attr=%s ", name, codestring(attr));

	/* For networks we have to do some monkeying around with the peerip in order to
	 * potentially find a network that matches.  We start at a mask of 32 bits and try
	 * to find a match in the hash table.  We continue to try to find a match by reducing
	 * the mask until we either find a match or exhaust our mask.  By doing it this way,
	 * we pick up the highest specificity for our network match.
	 */

	if ((return_val = inet_pton(AF_INET, name, (void *) &peer_addr)) != 1) {
		if (debug & DEBUG_CONFIG_FLAG) {
			report(LOG_DEBUG, "cfg_get_nvalue: no net for %s", name);
		}
		return (value);
	}

	peer_int_addr = htonl(peer_addr.s_addr);

	mask = 0xffffffff;

	for (i = 0; i < 32; i++, mask = mask << 1) {
		network = peer_int_addr & mask;

		sprintf(cidr, "%d.%d.%d.%d/%d", ((network & 0xff000000) >> 24), ((network & 0xff0000) >> 16), ((network & 0xff00) >> 8),
				network & 0xff, 32 - i);

		/* Look up the network in the hash table */
		net = (NET *) hash_lookup(nettable, cidr);
		if (net) {
			if (debug & DEBUG_CONFIG_FLAG) {
				report(LOG_DEBUG, "cfg_get_nvalue: net found: %s", cidr);
			}
			value = get_nvalue(net, attr);
			/* If we get a valid pval, return it, otherwise continue iterating looking for a valid value
			 * for the attribute of interest.  This allows us to provide attributes lower in the structure
			 * that override attributes higher in the structure, and have a general attribute at the higher
			 * end of the configuration structure (ie. we can have prompt values for specific subnets, but
			 * have a key that covers all of those generalized subnets)
			 */
			if (value.pval) {
				if (debug & DEBUG_CONFIG_FLAG) {
					report(LOG_DEBUG, "cfg_get_nvalue: value found for name=%s attr=%s", cidr, value);
				}
				return (value);
			}
		}
	}

	if (debug & DEBUG_CONFIG_FLAG) {
		report(LOG_DEBUG, "cfg_get_nvalue: no net for %s", name);
	}
	return (value);
}

/* Wrappers for cfg_get_value */
int cfg_get_intvalue(char *name, int isuser, int attr, int recurse)
{
	int val;

	val = cfg_get_value(name, isuser, attr, recurse).intval;

	if (debug & DEBUG_CONFIG_FLAG)
		report(LOG_DEBUG, "cfg_get_intvalue: returns %d", val);
	return (val);
}

/* Wrappers for cfg_get_hvalue */
char *
cfg_get_phvalue(char *name, int attr)
{
	char *p;

	p = cfg_get_hvalue(name, attr).pval;

	if (debug & DEBUG_CONFIG_FLAG)
		report(LOG_DEBUG, "cfg_get_phvalue: returns %s", p ? p : "NULL");

	return (p);
}

/* Wrappers for cfg_get_nvalue */
char *
cfg_get_pnvalue(char *name, int attr)
{
	char *p;

	p = cfg_get_nvalue(name, attr).pval;

	if (debug & DEBUG_CONFIG_FLAG)
		report(LOG_DEBUG, "cfg_get_pnvalue: returns %s", p ? p : "NULL");

	return (p);
}

char *
cfg_get_pvalue(char *name, int isuser, int attr, int recurse)
{
	char *p;

	p = cfg_get_value(name, isuser, attr, recurse).pval;

	if (debug & DEBUG_CONFIG_FLAG)
		report(LOG_DEBUG, "cfg_get_pvalue: returns %s", p ? p : "NULL");

	return (p);
}

/*
 * Read the config file and do some basic sanity checking on it.  Return 1
 * if we find any errors.
 */
int cfg_read_config(char *cfile)
{
	sym_line = 1;

	if ((cf = fopen(cfile, "r")) == NULL) {
		report(LOG_ERR, "read_config: fopen() error for file %s %s, exiting", cfile, strerror(errno));
		return (1);
	}
	if (parse_decls() || sym_error) {
		fclose(cf);
		return (1);
	}

	if (circularity_check()) {
		fclose(cf);
		return (1);
	}

	fclose(cf);
	return (0);
}

/* return 1 if user exists, 0 otherwise */
int cfg_user_exists(char *username)
{
	USER *user;

	user = (USER *) hash_lookup(usertable, username);

	return (user != NULL);
}

/*
 * return expiry string of user. If none, try groups she is a member
 * of, and so on, recursively if recurse is non-zero
 */
char *
cfg_get_expires(char *username, int recurse)
{
	return (cfg_get_pvalue(username, TAC_IS_USER, S_expires, recurse));
}

#ifdef ACLS

/*
 * check the acl against the provided ip.  return S_permit (succeed) if the
 * ip matches a permit, else S_deny (fail) if it matches a deny or does not
 * match any of the entries.
 */
int
cfg_acl_check(char *aclname, char *ip)
{
	NODE *next;
	ACL *acl;

	acl = (ACL *) hash_lookup(acltable, aclname);

	if (debug & DEBUG_AUTHEN_FLAG)
		report(LOG_DEBUG, "cfg_acl_check(%s, %s)", aclname, ip);

	if (acl == NULL) {
		report(LOG_ERR, "non-existent acl reference %s", aclname);
		return (S_deny);
	}

	next = acl->nodes;
	while (next) {
		if (regexec((regex_t *) next->value1, ip, 0, NULL, 0) == REG_OK) {
			if (debug & DEBUG_AUTHEN_FLAG)
				report(LOG_DEBUG, "ip %s matched %s regex %s of acl filter %s",
					ip, next->type == S_deny ? "deny" : "permit",
					next->value, aclname);
			return (next->type);
		}
		next = next->next;
	}

	/* default is fail (implicit deny) - ie: fell off the end */
	if (debug & DEBUG_AUTHEN_FLAG)
		report(LOG_DEBUG, "ip %s did not match in acl filter %s", ip, aclname);
	return (S_deny);
}
#endif

#if defined(UENABLE) || defined(SKEY)

/*
 * return enable password string of user.  If none, try groups user is a
 * member of, and so on, recursively if recurse is non-zero.
 */
char *
cfg_get_enable_secret(char *user, int recurse)
{
	return (cfg_get_pvalue(user, TAC_IS_USER, S_enable, recurse));
}
#endif

/* For getting host key */
char *
cfg_get_host_key(char *host)
{
	return (cfg_get_phvalue(host, S_key));
}

/* For getting net key */
char *
cfg_get_net_key(char *net)
{
	return (cfg_get_pnvalue(net, S_key));
}

/* For getting host prompt */
char *
cfg_get_host_prompt(char *host)
{
	return (cfg_get_phvalue(host, S_prompt));
}

/* For getting net prompt */
char *
cfg_get_net_prompt(char *net)
{
	return (cfg_get_phvalue(net, S_prompt));
}

/* For getting host enable */
char *
cfg_get_host_enable(char *host)
{
	return (cfg_get_phvalue(host, S_enable));
}

/* For getting net enable */
char *
cfg_get_net_enable(char *net)
{
	return (cfg_get_phvalue(net, S_enable));
}

#ifdef UENABLE

/*
 * return value of the noenablepwd field for the host.
 */
int
cfg_get_host_noenablepwd(char *host)
{
	return (cfg_get_hvalue(host, S_noenablepwd).intval);
}
#endif

/*
 * return password string of user.  If none, try groups the user is a member
 * of, and so on, recursively if recurse is non-zero.
 */
char *
cfg_get_login_secret(char *user, int recurse)
{
	return (cfg_get_pvalue(user, TAC_IS_USER, S_login, recurse));
}

#ifdef UENABLE 

/*
 * return value of the noenablepwd field.  If none, try groups the user is a
 * member of, and so on, recursively if recurse is non-zero.
 */
int
cfg_get_user_noenablepwd(char *user, int recurse)
{
	return (cfg_get_intvalue(user, TAC_IS_USER, S_noenablepwd, recurse));
}
#endif

/*
 * return value of the nopasswd field.  If none, try groups the user is a
 * member of, and so on, recursively if recurse is non-zero.
 */
int cfg_get_user_nopasswd(char *user, int recurse)
{
	return (cfg_get_intvalue(user, TAC_IS_USER, S_nopasswd, recurse));
}

/*
 * return the secret of the user.  If none, try groups the user is a member of,
 * and so on, recursively if recurse is non-zero.
 */
char *
cfg_get_arap_secret(char *user, int recurse)
{
	return (cfg_get_pvalue(user, TAC_IS_USER, S_arap, recurse));
}

char *
cfg_get_chap_secret(char *user, int recurse)
{
	return (cfg_get_pvalue(user, TAC_IS_USER, S_chap, recurse));
}

#ifdef MSCHAP

char *
cfg_get_mschap_secret(char *user, int recurse)
{
	return (cfg_get_pvalue(user, TAC_IS_USER, S_mschap, recurse));
}
#endif /* MSCHAP */

char *
cfg_get_pap_secret(char *user, int recurse)
{
	return (cfg_get_pvalue(user, TAC_IS_USER, S_pap, recurse));
}

char *
cfg_get_opap_secret(char *user, int recurse)
{
	return (cfg_get_pvalue(user, TAC_IS_USER, S_opap, recurse));
}

/* return the global password for the user (or the group, etc.) */
char *
cfg_get_global_secret(char *user, int recurse)
{
	return (cfg_get_pvalue(user, TAC_IS_USER, S_global, recurse));
}

/*
 * Return a pointer to a node representing a given service authorization,
 * taking care of recursion issues correctly.  Protocol is only read if the
 * type is N_svc_ppp. svcname is only read if type is N_svc.
 */
NODE *
cfg_get_svc_node(char *username, int type, char *protocol, char *svcname, int recurse)
{
	USER *user, *group;
	NODE *svc;

	if (debug & DEBUG_CONFIG_FLAG)
		report(LOG_DEBUG, "cfg_get_svc_node: username=%s %s proto=%s svcname=%s rec=%d", username, cfg_nodestring(type),
			protocol ? protocol : "", svcname ? svcname : "", recurse);

	/* find the user/group entry */
	user = (USER *) hash_lookup(usertable, username);

	if (!user) {
		if (debug & DEBUG_CONFIG_FLAG)
			report(LOG_DEBUG, "cfg_get_svc_node: no user named %s", username);
		return (NULL);
	}

	/* found the user entry. Find svc node */
	for (svc = (NODE *) get_value(user, S_svc).pval; svc; svc = svc->next) {

		if (svc->type != type)
			continue;

		if (type == N_svc_ppp && !STREQ(svc->value1, protocol)) {
			continue;
		}

		if (type == N_svc && !STREQ(svc->value1, svcname)) {
			continue;
		}

		if (debug & DEBUG_CONFIG_FLAG)
			report(LOG_DEBUG, "cfg_get_svc_node: found %s proto=%s svcname=%s", cfg_nodestring(type), protocol ? protocol : "",
				svcname ? svcname : "");

		return (svc);
	}

	if (!recurse) {
		if (debug & DEBUG_CONFIG_FLAG)
			report(LOG_DEBUG, "cfg_get_svc_node: returns NULL");
		return (NULL);
	}

	/* no matching node. Check containing group */
	if (user->member)
		group = (USER *) hash_lookup(grouptable, user->member);
	else
		group = NULL;

	while (group) {
		if (debug & DEBUG_CONFIG_FLAG)
			report(LOG_DEBUG, "cfg_get_svc_node: recurse group = %s", group->name);

		for (svc = (NODE *) get_value(group, S_svc).pval; svc; svc = svc->next) {

			if (svc->type != type)
				continue;

			if (type == N_svc_ppp && !STREQ(svc->value1, protocol)) {
				continue;
			}

			if (type == N_svc && !STREQ(svc->value1, svcname)) {
				continue;
			}

			if (debug & DEBUG_CONFIG_FLAG)
				report(LOG_DEBUG, "cfg_get_svc_node: found %s proto=%s svcname=%s", cfg_nodestring(type), protocol ? protocol : "",
					svcname ? svcname : "");

			return (svc);
		}

		/* still nothing. Check containing group and so on */

		if (group->member)
			group = (USER *) hash_lookup(grouptable, group->member);
		else
			group = NULL;
	}

	if (debug & DEBUG_CONFIG_FLAG)
		report(LOG_DEBUG, "cfg_get_svc_node: returns NULL");

	/* no matching svc node for this user or her containing groups */
	return (NULL);
}

/*
 * Return a pointer to the node representing a set of command regexp matches
 * for a user and command, handling recursion issues correctly.
 */
NODE *
cfg_get_cmd_node(char *name, char *cmdname, int recurse)
{
	USER *user, *group;
	NODE *svc;

	if (debug & DEBUG_CONFIG_FLAG)
		report(LOG_DEBUG, "cfg_get_cmd_node: name=%s cmdname=%s rec=%d", name, cmdname, recurse);

	/* find the user/group entry */
	user = (USER *) hash_lookup(usertable, name);

	if (!user) {
		if (debug & DEBUG_CONFIG_FLAG)
			report(LOG_DEBUG, "cfg_get_cmd_node: no user named %s", name);
		return (NULL);
	}
	/* found the user entry. Find svc node */
	svc = (NODE *) get_value(user, S_svc).pval;

	while (svc) {
		if (svc->type == N_svc_cmd && STREQ(svc->value, cmdname)) {
			if (debug & DEBUG_CONFIG_FLAG)
				report(LOG_DEBUG, "cfg_get_cmd_node: found cmd %s %s node", cmdname, cfg_nodestring(svc->type));
			return (svc);
		}
		svc = svc->next;
	}

	if (!recurse) {
		if (debug & DEBUG_CONFIG_FLAG)
			report(LOG_DEBUG, "cfg_get_cmd_node: returns NULL");
		return (NULL);
	}
	/* no matching node. Check containing group */
	if (user->member)
		group = (USER *) hash_lookup(grouptable, user->member);
	else
		group = NULL;

	while (group) {
		if (debug & DEBUG_CONFIG_FLAG)
			report(LOG_DEBUG, "cfg_get_cmd_node: recurse group = %s", group->name);

		svc = get_value(group, S_svc).pval;

		while (svc) {
			if (svc->type == N_svc_cmd && STREQ(svc->value, cmdname)) {
				if (debug & DEBUG_CONFIG_FLAG)
					report(LOG_DEBUG, "cfg_get_cmd_node: found cmd %s node %s", cmdname, cfg_nodestring(svc->type));
				return (svc);
			}
			svc = svc->next;
		}

		/* still nothing. Check containing group and so on */

		if (group->member)
			group = (USER *) hash_lookup(grouptable, group->member);
		else
			group = NULL;
	}

	if (debug & DEBUG_CONFIG_FLAG)
		report(LOG_DEBUG, "cfg_get_cmd_node: returns NULL");

	/* no matching cmd node for this user or her containing groups */
	return (NULL);
}

/*
 * Return an array of character strings representing configured AV
 * pairs, given a username and a service node.
 *
 * In the AV strings returned, manipulate the separator character to
 * indicate which args are optional and which are mandatory.
 *
 * Lastly, indicate what default permission was configured by setting denyp
 */
char **
cfg_get_svc_attrs(NODE *svcnode, int *denyp)
{
	int i;
	NODE *node;
	char **args;

	*denyp = 1;

	if (!svcnode)
		return (NULL);

	*denyp = (svcnode->dflt == S_deny);

	i = 0;
	for (node = svcnode->value; node; node = node->next)
		i++;

	args = (char **) tac_malloc(sizeof (char *) * (i + 1));

	i = 0;
	for (node = svcnode->value; node; node = node->next) {
		char *arg = tac_strdup(node->value);
		char *p = strchr(arg, '=');

		if (p && node->type == N_optarg)
			*p = '*';
		args[i++] = arg;
	}
	args[i] = NULL;
	return (args);
}

int cfg_user_svc_default_is_permit(char *user)
{
	int permit;

	permit = cfg_get_intvalue(user, TAC_IS_USER, S_svc_dflt, TAC_PLUS_RECURSE);

	switch (permit) {
		default: /* default is deny */
		case S_deny:
			return (0);
		case S_permit:
			return (1);
	}
}

char *
cfg_get_authen_default(void)
{
	return (authen_default);
}

/*
 * Return 1 if this user has any ppp services configured. Used for
 * authorizing ppp/lcp requests
 */
int cfg_ppp_is_configured(char *username, int recurse)
{
	USER *user, *group;
	NODE *svc;

	if (debug & DEBUG_CONFIG_FLAG)
		report(LOG_DEBUG, "cfg_ppp_is_configured: username=%s rec=%d", username, recurse);

	/* find the user/group entry */
	user = (USER *) hash_lookup(usertable, username);

	if (!user) {
		if (debug & DEBUG_CONFIG_FLAG)
			report(LOG_DEBUG, "cfg_ppp_is_configured: no user named %s", username);
		return (0);
	}

	/* found the user entry. Find svc node */
	for (svc = (NODE *) get_value(user, S_svc).pval; svc; svc = svc->next) {

		if (svc->type != N_svc_ppp)
			continue;

		if (debug & DEBUG_CONFIG_FLAG)
			report(LOG_DEBUG, "cfg_ppp_is_configured: found svc ppp %s node", svc->value1);

		return (1);
	}

	if (!recurse) {
		if (debug & DEBUG_CONFIG_FLAG)
			report(LOG_DEBUG, "cfg_ppp_is_configured: returns 0");
		return (0);
	}

	/* no matching node. Check containing group */
	if (user->member)
		group = (USER *) hash_lookup(grouptable, user->member);
	else
		group = NULL;

	while (group) {
		if (debug & DEBUG_CONFIG_FLAG)
			report(LOG_DEBUG, "cfg_ppp_is_configured: recurse group = %s", group->name);

		for (svc = (NODE *) get_value(group, S_svc).pval; svc; svc = svc->next) {

			if (svc->type != N_svc_ppp)
				continue;

			if (debug & DEBUG_CONFIG_FLAG)
				report(LOG_DEBUG, "cfg_ppp_is_configured: found svc ppp %s "
					"node", svc->value1);

			return (1);
		}

		/* still nothing. Check containing group and so on */

		if (group->member)
			group = (USER *) hash_lookup(grouptable, group->member);
		else
			group = NULL;
	}

	if (debug & DEBUG_CONFIG_FLAG)
		report(LOG_DEBUG, "cfg_ppp_is_configured: returns 0");

	/* no PPP svc nodes for this user or her containing groups */
	return (0);
}
